package com.eastfair.venueservice.service;

import cn.hutool.core.date.LocalDateTimeUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.eastfair.boot.request.PageParams;
import com.eastfair.boot.service.SuperService;
import com.eastfair.venueservice.dto.ExhibitionSettlementDTO;
import com.eastfair.venueservice.dto.WorkOvertimeDTO;
import com.eastfair.venueservice.entity.WorkOvertime;
import com.eastfair.venueservice.enumeration.ExhibitionSettlementDetailSettlementTypeEnum;
import com.eastfair.venueservice.enumeration.WorkOvertimePayStatusEnum;
import com.eastfair.venueservice.vo.ExhibitionSettlementDetailVO;
import com.eastfair.venueservice.vo.ExhibitionSettlementVO;
import com.eastfair.venueservice.vo.WorkOvertimeVO;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

/**
 * <p>
 * 业务接口
 * 加班申请
 * </p>
 *
 * @author dq
 * @date 2022-10-19
 */
public interface WorkOvertimeService extends SuperService<WorkOvertime> {

    /**
     * 分页查询加班申请列表VO.
     *
     * @param pageParams   the page params
     * @param queryCreator the query creator
     * @return the page
     */
    Page<WorkOvertimeVO> listWorkOvertimeVO(PageParams<WorkOvertimeDTO> pageParams, Function<WorkOvertimeDTO, QueryWrapper<WorkOvertime>> queryCreator);

    /**
     * 主场搭建商查询加班申请列表.
     *
     * @param pageParams the page params
     * @return the list
     */
    Page<WorkOvertimeVO> listForHomeBuilder(PageParams<WorkOvertimeDTO> pageParams);

    /**
     * 场馆查询加班申请列表.
     *
     * @param pageParams the page params
     * @return the list
     */
    Page<WorkOvertimeVO> listForVenue(PageParams<WorkOvertimeDTO> pageParams);

    /**
     * 查询加班列表VO.
     *
     * @param query the query
     * @return the list
     */
    List<WorkOvertimeVO> listWorkOvertimeVO(WorkOvertimeDTO query);

    /**
     * 查询加班申请.
     *
     * @param workOvertimeDTO the work overtime dto
     * @return the list
     */
    List<WorkOvertime> listWorkOvertime(WorkOvertimeDTO workOvertimeDTO);

    /**
     * 查询待结算的场馆加班单
     *
     * @param query the query
     * @return the list
     */
    List<WorkOvertimeVO> listForVenueWorkOvertimeSettlement(WorkOvertimeDTO query);

    /**
     * 查询场馆加班申请的结算单
     *
     * @param query the query
     * @return the venue work overtime settlement
     */
    ExhibitionSettlementVO getVenueWorkOvertimeSettlement(ExhibitionSettlementDTO query);

    /**
     * 查询待结算的主场加班单
     *
     * @param query the query
     * @return the list
     */
    List<WorkOvertimeVO> listForHomeWorkOvertimeSettlement(WorkOvertimeDTO query);

    /**
     * 查询主场加班申请的结算单
     *
     * @param query the query
     * @return the venue work overtime settlement
     */
    ExhibitionSettlementVO getHomeWorkOvertimeSettlement(ExhibitionSettlementDTO query);

    /**
     * 加班单号获取加班详情VO.
     *
     * @param workOvertimeNumber the work overtime number
     * @return the vo by work overtime number
     */
    WorkOvertimeVO getVOByWorkOvertimeNumber(String workOvertimeNumber);

    /**
     * 订单号获取加班申请.
     *
     * @param orderNumber the order number
     * @return the vo by order number
     */
    WorkOvertimeVO getVOByOrderNumber(String orderNumber);

    /**
     * 加班单号获取加班详情.
     *
     * @param workOvertimeNumber the work overtime number
     * @return the by work overtime number
     */
    WorkOvertime getByWorkOvertimeNumber(String workOvertimeNumber);

    /**
     * 通过订单号查询加班申请
     *
     * @param orderNumber the order number
     * @return by order number
     */
    WorkOvertime getByOrderNumber(String orderNumber);

    /**
     * 保存加班申请.
     *
     * @param workOvertimeDTO the work overtime dto
     * @return the boolean
     */
    WorkOvertime saveWorkOvertime(WorkOvertimeDTO workOvertimeDTO);


    /**
     * 保存展位类型加班规则.
     *
     * @param workOvertimeDTO the work overtime dto
     * @return the work overtime
     */
    WorkOvertime saveHomeBuilderSiteTypeWorkOvertime(WorkOvertimeDTO workOvertimeDTO);

    /**
     * 更新加班申请.
     *
     * @param workOvertimeDTO the work overtime dto
     * @return the boolean
     */
    WorkOvertime updateWorkOvertime(WorkOvertimeDTO workOvertimeDTO);


    /**
     * 更新展位类型加班规则.
     *
     * @param workOvertimeDTO the work overtime dto
     * @return the work overtime
     */
    WorkOvertime updateHomeBuilderSiteTypeWorkOvertime(WorkOvertimeDTO workOvertimeDTO);

    /**
     * 搭建商申请加班.
     *
     * @param workOvertimeDTO the work overtime dto
     * @return the boolean
     */
    boolean submitForBuilder(WorkOvertimeDTO workOvertimeDTO);

    /**
     * 主场申请加班.
     *
     * @param workOvertimeDTO the work overtime dto
     * @return the boolean
     */
    boolean submitForHome(WorkOvertimeDTO workOvertimeDTO);

    /**
     * 主场向场馆提交加班申请.
     *
     * @param workOvertime the work overtime dto
     * @return the boolean
     */
    boolean submitHomeWorkOvertimeTOVenue(WorkOvertime workOvertime);

    /**
     * 更新加班为待审核.
     *
     * @param workOvertimeNumber the work overtime number
     * @return the work overtime
     */
    WorkOvertime updateWorkOvertimeSubmit(String workOvertimeNumber);

    /**
     * 更新加班申请状态为已取消.
     *
     * @param workOvertime the work overtime
     * @return the boolean
     */
    WorkOvertime updateWorkOvertimeCancel(WorkOvertime workOvertime);


    /**
     * 更新审核结果.
     *
     * @param workOvertimeDTO the work overtime dto
     * @return the boolean
     */
    WorkOvertime updateWorkOvertimeAuditResult(WorkOvertimeDTO workOvertimeDTO);

    /**
     * 更新支付结果.
     *
     * @param workOvertimeDTO the work overtime dto
     * @return the boolean
     */
    boolean updateWorkOvertimePayStatus(WorkOvertimeDTO workOvertimeDTO);


    /**
     * 加班申请审核.
     *
     * @param workOvertimeDTO the work overtime dto
     * @return the boolean
     */
    boolean auditBuilderWorkOvertime(WorkOvertimeDTO workOvertimeDTO);

    /**
     * 搭建商取消加班申请.
     *
     * @param workOvertimeNumber the work overtime number
     * @return the boolean
     */
    boolean cancelBuilderWorkOvertime(String workOvertimeNumber);

    /**
     * 主场取消加班申请.
     *
     * @param workOvertimeNumber the work overtime number
     * @return the boolean
     */
    boolean cancelHomeWorkOvertime(String workOvertimeNumber);


    /**
     * 取消场馆的加班申请
     *
     * @param workOvertimeNumber the work overtime number
     * @return the boolean
     */
    boolean cancelVenueWorkOvertime(String workOvertimeNumber);


    /**
     * 加班单号删除.
     *
     * @param workOvertimeNumber the ids
     * @return the boolean
     */
    boolean removeByWorkOvertimeNumber(String workOvertimeNumber);


    /**
     * 保存交易记录.
     *
     * @param workOvertime    the work overtime
     * @param workOvertimeDTO the work overtime dto
     * @return the boolean
     */
    boolean saveTransaction(WorkOvertime workOvertime, WorkOvertimeDTO workOvertimeDTO);

    /**
     * 保存支付系统交易订单.
     *
     * @param workOvertimeDTO the work overtime dto
     * @return the boolean
     */
    boolean saveTransactionOrder(WorkOvertimeDTO workOvertimeDTO);

    /**
     * 保存场馆加班订单到支付系统.
     *
     * @param workOvertimeNumber the work overtime number
     * @return the boolean
     */
    boolean saveVenueTransactionOrder(String workOvertimeNumber);

    /**
     * 保存主场加班订单到支付系统.
     *
     * @param workOvertimeNumber the work overtime number
     * @return the boolean
     */
    boolean saveHomeTransactionOrder(String workOvertimeNumber);

    /**
     * 发送配置收款商户的站内信
     *
     * @param workOvertimeDTO the work overtime dto
     * @return the boolean
     */
    boolean sendConfigHomePayeeInnerMsg(WorkOvertimeDTO workOvertimeDTO);

    /**
     * 发送配置收款商户的站内信
     *
     * @param workOvertimeDTO the work overtime dto
     * @return the boolean
     */
    void asyncSendConfigHomePayeeInnerMsg(WorkOvertimeDTO workOvertimeDTO);

    /**
     * 计算加班正常计费总金额.
     *
     * @param periodOnePrice the period one price
     * @param periodOne      the period one
     * @param periodTwoPrice the period two price
     * @param periodTwo      the period two
     * @param timeUnit       the time unit
     * @param overtimeArea   the overtime area
     * @return the big decimal
     */
    static BigDecimal calculateOvertimeAmount(BigDecimal periodOnePrice, Double periodOne, BigDecimal periodTwoPrice, Double periodTwo, Integer timeUnit, BigDecimal overtimeArea) {
        // 计算时段1加班总金额
        BigDecimal periodOneBigDecimal = BigDecimal
                .valueOf(periodOne)
                .divide(BigDecimal.valueOf(timeUnit), 2, RoundingMode.DOWN);
        BigDecimal periodOneTotalAmount = periodOnePrice
                .multiply(overtimeArea)
                .multiply(periodOneBigDecimal)
                .setScale(2, RoundingMode.DOWN);
        // 计算时段2加班总金额
        BigDecimal periodTwoBigDecimal = BigDecimal
                .valueOf(periodTwo)
                .divide(BigDecimal.valueOf(timeUnit), 2, RoundingMode.DOWN);
        ;
        BigDecimal periodTwoTotalAmount = periodTwoPrice
                .multiply(overtimeArea)
                .multiply(periodTwoBigDecimal)
                .setScale(2, RoundingMode.DOWN);
        // 计算时段1总金额 + 时段2总金额2 = 加班总金额
        return periodOneTotalAmount.add(periodTwoTotalAmount);
    }

    /**
     * 计算超时加急费用.
     *
     * @param overtimeAmount the overtime amount
     * @param surchargeRate  the surcharge rate
     * @return the big decimal
     */
    static BigDecimal calculateOvertimeSurcharge(BigDecimal overtimeAmount, BigDecimal surchargeRate) {
        // 计算超时加急费
        return overtimeAmount
                .multiply(surchargeRate)
                .setScale(2, RoundingMode.DOWN);
    }

    /**
     * 计算加班应付总金额
     * 公式:计费单价 * 起订面积 * 计价时长 * 超时费率.
     *
     * @param periodOnePrice the period one price
     * @param periodOne      the period one
     * @param periodTwoPrice the period two price
     * @param periodTwo      the period two
     * @param timeUnit       the time unit
     * @param overtimeArea   the overtime area
     * @param surchargeRate  the surcharge rate
     * @return the big decimal
     */
    static BigDecimal calculateShouldPayAmount(BigDecimal periodOnePrice, Double periodOne, BigDecimal periodTwoPrice, Double periodTwo, Integer timeUnit, BigDecimal overtimeArea, BigDecimal surchargeRate) {
        BigDecimal overtimeAmount = calculateOvertimeAmount(periodOnePrice, periodOne, periodTwoPrice, periodTwo, timeUnit, overtimeArea);
        BigDecimal overtimeSurcharge = calculateOvertimeSurcharge(overtimeAmount, surchargeRate);
        return overtimeAmount.add(overtimeSurcharge);
    }

    /**
     * 计算加班面积.
     *
     * @param siteArea the site area
     * @param minArea  the min area
     * @return the big decimal
     */
    static BigDecimal calculateOvertimeArea(BigDecimal siteArea, BigDecimal minArea) {
        // 计算加班面积
        BigDecimal overtimeArea;
        if (siteArea.compareTo(minArea) >= 0) {
            overtimeArea = siteArea;
        } else {
            overtimeArea = minArea;
        }
        return overtimeArea;
    }


    /**
     * 计算时段内的加班开始时间和结束时间
     *
     * @param submitPeriodBegin the submit period begin
     * @param submitPeriodEnd   the submit period end
     * @param overtimeStart     the overtime start
     * @param overtimeEnd       the overtime end
     * @param overtimeDate      the overtime date
     * @return the map
     */
    static Map<String, LocalTime> calculatePeriodBeginAndEnd(LocalTime submitPeriodBegin, LocalTime submitPeriodEnd, LocalTime overtimeStart, LocalTime overtimeEnd, LocalDate overtimeDate) {
        LocalTime periodBegin = null;
        LocalTime periodEnd = null;
        // 计算计时时段的日期时间，便于判断是否在范围内
        LocalDateTime submitPeriodBeginDateTime = calculateOvertimeDateTime(submitPeriodBegin, overtimeDate);
        LocalDateTime submitPeriodEndDateTime = calculateOvertimeDateTime(submitPeriodEnd, overtimeDate);
        // 计算加班时间的日期时间，便于判断是否在范围内
        LocalDateTime overtimeStartDateTime = calculateOvertimeDateTime(overtimeStart, overtimeDate);
        LocalDateTime overtimeEndDateTime = calculateOvertimeDateTime(overtimeEnd, overtimeDate);
        boolean startBeforePeriod = overtimeStartDateTime.isAfter(submitPeriodBeginDateTime) || overtimeStartDateTime.equals(submitPeriodBeginDateTime);
        boolean startAfterPeriod = overtimeStartDateTime.isBefore(submitPeriodEndDateTime) || overtimeStartDateTime.equals(submitPeriodEndDateTime);
        boolean endAfterPeriod = overtimeEndDateTime.isAfter(submitPeriodBeginDateTime) || overtimeEndDateTime.equals(submitPeriodBeginDateTime);
        boolean endBeforePeriod = overtimeEndDateTime.isBefore(submitPeriodEndDateTime) || overtimeEndDateTime.equals(submitPeriodEndDateTime);
        // 如果加班开始时间在规则时段范围内，则该时段加班开始时间就是加班开始时间
        if (startBeforePeriod && startAfterPeriod) {
            periodBegin = overtimeStart;
            // 如果加班结束时间在时段规则时段范围内，则该时段加班结束时间就是加班结束时间
            if (endAfterPeriod && endBeforePeriod) {
                periodEnd = overtimeEnd;
            } else {
                // 否则，该时段加班结束时间为规则时段结束时间
                periodEnd = submitPeriodEnd;
            }
        } else {
            // 如果加班开始时间不在规则时段范围内，加班结束时间在规则时段范围内，则该时段加班开始时间是规则时段开始时间，该时段加班结束时间是加班结束时间
            if (endAfterPeriod && endBeforePeriod) {
                periodBegin = submitPeriodBegin;
                periodEnd = overtimeEnd;
            }
            // 如果加班开始时间结束时间都不在规则时段内，则该时段未申请加班
        }
        // 如果开始时间和结束时间相等，则在此时段未加班
        if (periodBegin != null && periodEnd != null && periodBegin.equals(periodEnd)) {
            periodBegin = null;
            periodEnd = null;
        }
        Map<String, LocalTime> periodMap = new HashMap<>(16);
        periodMap.put("begin", periodBegin);
        periodMap.put("end", periodEnd);
        return periodMap;
    }

    /**
     * 计算加班的日期时间
     *
     * @param overtimeTime the overtime time
     * @param overTimeDate the over time date
     * @return the local date time
     */
    static LocalDateTime calculateOvertimeDateTime(LocalTime overtimeTime, LocalDate overTimeDate) {
        LocalTime twentyClock = LocalTime.of(12, 0, 0);
        LocalTime zeroClock = LocalTime.of(23, 59, 59);
        LocalDateTime overtimeDateTime;
        if ((overtimeTime.isAfter(twentyClock) || overtimeTime.equals(twentyClock)) && (overtimeTime.isBefore(zeroClock))) {
            overtimeDateTime = LocalDateTime.of(overTimeDate, overtimeTime);
        } else {
            LocalDate overtimeDatePlus = overTimeDate.plus(1, ChronoUnit.DAYS);
            overtimeDateTime = LocalDateTime.of(overtimeDatePlus, overtimeTime);
        }
        return overtimeDateTime;
    }

    /**
     * 计算时段区间的加班时长
     *
     * @param periodBegin  the period begin
     * @param periodEnd    the period end
     * @param overtimeDate the overtime date
     * @return the double
     */
    static double calculatePeriod(LocalTime periodBegin, LocalTime periodEnd, LocalDate overtimeDate) {
        // 计算时段区间的加班时间段
        if (periodBegin == null || periodEnd == null) {
            return 0d;
        }
        LocalDateTime periodOneBeginDateTime = calculateOvertimeDateTime(periodBegin, overtimeDate);
        LocalDateTime periodOneEndDateTime = calculateOvertimeDateTime(periodEnd, overtimeDate);
        return (double) LocalDateTimeUtil.between(periodOneBeginDateTime, periodOneEndDateTime, ChronoUnit.HOURS);
    }

    /**
     * 计算加班结算单
     *
     * @param workOvertimeList the work overtime list
     * @return the exhibition settlement vo
     */
    static ExhibitionSettlementVO calculateExhibitionSettlement(List<WorkOvertimeVO> workOvertimeList) {
        BigDecimal totalPayAmount = BigDecimal.ZERO;
        ExhibitionSettlementVO exhibitionSettlementVO = new ExhibitionSettlementVO();
        List<ExhibitionSettlementDetailVO> exhibitionSettlementDetailList = new ArrayList<>();
        for (WorkOvertimeVO workOvertimeVO: workOvertimeList) {
            if (WorkOvertimePayStatusEnum.HAS_PAY.eq(workOvertimeVO.getPayStatus()) || WorkOvertimePayStatusEnum.PARTIAL_PAY.eq(workOvertimeVO.getPayStatus())) {
                BigDecimal actualPayAmount = workOvertimeVO.getActualPayAmount() == null ? BigDecimal.ZERO : workOvertimeVO.getActualPayAmount();
                totalPayAmount = totalPayAmount.add(actualPayAmount);
            }
            ExhibitionSettlementDetailVO exhibitionSettlementDetailVO = WorkOvertimeService.toExhibitionSettlementDetail(workOvertimeVO);
            exhibitionSettlementDetailList.add(exhibitionSettlementDetailVO);
        }
        exhibitionSettlementVO.setReceived(totalPayAmount);
        exhibitionSettlementVO.setExhibitionSettlementDetail(exhibitionSettlementDetailList);
        return exhibitionSettlementVO;
    }

    /**
     * 加班单转为结算单明细
     *
     * @param workOvertimeVO the work overtime vo
     * @return the exhibition settlement detail vo
     */
    static ExhibitionSettlementDetailVO toExhibitionSettlementDetail(WorkOvertimeVO workOvertimeVO) {
        ExhibitionSettlementDetailVO exhibitionSettlementDetailVO = new ExhibitionSettlementDetailVO();
        exhibitionSettlementDetailVO.setSettlementType(ExhibitionSettlementDetailSettlementTypeEnum.WORK_OVERTIME);
        exhibitionSettlementDetailVO.setBusinessNo(workOvertimeVO.getWorkOvertimeNumber());
        exhibitionSettlementDetailVO.setName("加班");
        exhibitionSettlementDetailVO.setNum(workOvertimeVO.getReportOvertimePeriod().longValue());
        exhibitionSettlementDetailVO.setSubtotalPrice(workOvertimeVO.getShouldPayAmount());
        return exhibitionSettlementDetailVO;
    }
}
